package com.mcintyret.site.journal.database;

import com.google.common.collect.Lists;
import com.mcintyret.site.context.UserContext;
import com.mcintyret.site.journal.*;
import com.mcintyret.site.users.OwnedByUser;
import org.hibernate.SessionFactory;
import org.springframework.stereotype.Repository;

import javax.annotation.Resource;
import java.io.Serializable;
import java.util.List;

/**
 * User: tommcintyre
 * Date: 3/29/12
 */

@Repository
public abstract class AbstractJournalDao<T extends OwnedByUser> extends AbstractHibernateDao<T> {

    @Resource
    protected UserContext userContext;

    protected AbstractJournalDao(Class<T> clazz) {
        super(clazz);
    }

    @Override
    public T get(Serializable id) {
        T t = super.get(id);
        if (isOperationAllowed(t)) {
            return t;
        } else {
            throw new JournalAccessException(id, userContext.getUsername(), getClazz());
        }
    }

    public List<T> getAllForCurrentUser() {
        return getAllForUser(userContext.getUsername());
    }

    public List<T> getAllForUser(String username) {
        if (isOperationAllowed(username)) {
            return super.getAllWhere("username = '" + username + "'");
        } else {
            throw new JournalAccessException();
        }
    }

    public int getCountForCurrentUser() {
        return getCountForUser(userContext.getUsername());
    }

    public int getCountForUser(String username) {
        if (isOperationAllowed(username)) {
            return super.getCountWhere("username = '" + username + "'");
        } else {
            throw new JournalAccessException();
        }
    }

    public void delete(T t) {
        if (isOperationAllowed(t)) {
            super.delete(t);
        } else {
            throw new JournalAccessException();
        }
    }

    public void add(T t) {
        if (isOperationAllowed(t)) {
            super.add(t);
        }  else {
            throw new JournalAccessException();
        }
    }

    public Integer addAndGetId(T t) {
        if (isOperationAllowed(t)) {
            return (Integer) super.addAndGetId(t);
        } else {
           throw new JournalAccessException();
        }
    }

    private boolean isOperationAllowed(OwnedByUser ownedByUser) {
        return isOperationAllowed(ownedByUser.getUsername());
    }

    private boolean isOperationAllowed(String username) {
        return (userContext.isAdmin() || username.equals(userContext.getUsername()));
    }

    @Override
    @Resource(name = "journalSessionFactory")
    protected void setSessionFactory(SessionFactory sessionFactory) {
        this.sessionFactory = sessionFactory;
    }


    public void addNewDatapointToNumericalSequenceWithId(Integer timeseriesId, Datapoint newDatapoint) {
        Timeseries numSeq = getTimeseriesById(timeseriesId);
        numSeq.addDataPoint(newDatapoint);

        // Flush so that an Id is created now
        sessionFactory.getCurrentSession().flush();
    }

    public void addNewTextEntryToJournalWithId(Integer journalId, TextEntry textEntry) {
        Journal journal = getJournalById(journalId);
        journal.addTextEntry(textEntry);

        // Flush so that an Id is created now
        sessionFactory.getCurrentSession().flush();
    }

    public void addNewNumericalSequenceToJournalWithId(Integer journalId, Timeseries numSeq) {
        Journal journal = getJournalById(journalId);
        journal.addTimeseries(numSeq);

        // Flush so that an Id is created now
        sessionFactory.getCurrentSession().flush();
    }

    public int getNumberOfJournalFamilies() {
        String query = "select count(*) from JournalFamily where username = '" + userContext.getUsername() + "'";
        return ((Long) sessionFactory.getCurrentSession().createQuery(query).uniqueResult()).intValue();
    }


    public void addNewJournalToJournalFamilyWithId(Integer journalFamilyId, Journal journal) {
        JournalFamily journalFamily = getJournalFamilyById(journalFamilyId);
        journalFamily.addJournal(journal);
    }

    public void deleteJournalFamily(Integer journalFamilyId) {
        JournalFamily journalFamily = getJournalFamilyById(journalFamilyId);
        sessionFactory.getCurrentSession().delete(journalFamily);
    }

    public void deleteJournal(Integer journalId) {
        Journal journal = getJournalById(journalId);
        JournalFamily journalFamily = journal.getJournalFamily();
        journalFamily.getJournals().remove(journal);
    }

    public void deleteTextEntry(Integer textEntryId) {
        TextEntry textEntry = getTextEntryById(textEntryId);
        Journal journal = textEntry.getJournal();
        journal.getTextEntries().remove(textEntry);
    }

    public void deleteTimeseries(Integer timeseriesId) {
        Timeseries timeseries = getTimeseriesById(timeseriesId);
        Journal journal = timeseries.getJournal();
        journal.getTextEntries().remove(timeseries);
    }

    // Get a single instance of an object defined by its Id, and check that it belongs to the current user
    public JournalFamily getJournalFamilyById(Integer journalFamilyId) {
        return (JournalFamily) getPersistedObjectOfTypeWithId(JournalFamily.class, journalFamilyId);
    }

    public Journal getJournalById(Integer journalId) {
        return (Journal) getPersistedObjectOfTypeWithId(Journal.class, journalId);
    }

    public TextEntry getTextEntryById(Integer textEntryId) {
        return (TextEntry) getPersistedObjectOfTypeWithId(TextEntry.class, textEntryId);
    }

    public Timeseries getTimeseriesById(Integer timeseriesId) {
        return (Timeseries) getPersistedObjectOfTypeWithId(Timeseries.class, timeseriesId);
    }

    public Datapoint getDatapointById(Integer datapointId) {
        return (Datapoint) getPersistedObjectOfTypeWithId(Datapoint.class, datapointId);
    }

    // Get a list of all objects of the relevanat type
    public List<JournalFamily> getAllJournalFamilies() {
        return (List<JournalFamily>) getAllPersistedObjectsOfType(JournalFamily.class);
    }

    public List<Journal> getAllJournals() {
        return (List<Journal>) getAllPersistedObjectsOfType(Journal.class);
    }

    public List<TextEntry> getAllTextEntries() {
        return (List<TextEntry>) getAllPersistedObjectsOfType(TextEntry.class);
    }

    public List<Timeseries> getAllNumericSequences() {
        return (List<Timeseries>) getAllPersistedObjectsOfType(Timeseries.class);
    }

    // Core, class-agnostic methods to retrieve data items:
    private OwnedByUser getPersistedObjectOfTypeWithId(Class<? extends OwnedByUser> clazz, int id) {

        OwnedByUser persistedObject = (OwnedByUser) sessionFactory.getCurrentSession().get(clazz, id);
        if (!isValidForUser(persistedObject)) {
            throw new JournalAccessException(id, userContext.getUsername(), clazz);
        } else {
            return persistedObject;
        }

    }

    private boolean isValidForUser(OwnedByUser persistedObject) {
        return persistedObject != null &&
                persistedObject.getUsername() != null &&
                persistedObject.getUsername().equals(userContext.getUsername());
    }

    private List<?> getAllPersistedObjectsOfType(Class<?> clazz) {
        return getAllPersistedObjectsOfType(clazz, userContext.getUsername());
    }

    private List<?> getAllPersistedObjectsOfType(Class<?> clazz, String username) {
        String type = clazz.getSimpleName();
        String query = "From " + type + " as o where o.username = '" + username + "'";

        List<?> persistedObjects = sessionFactory.getCurrentSession().createQuery(query).list();
        return persistedObjects == null ? Lists.<Object>newArrayList() : persistedObjects;
    }

    public void addJournalFamily(JournalFamily journalFamily) {
        sessionFactory.getCurrentSession().persist(journalFamily);
    }

    public void flushSession() {
        sessionFactory.getCurrentSession().flush();
    }


}
